翻译版本,原文请见
这是第二部分的内容.
在第一部分,我们罗列了app的UI,开发和单元测试的基础.
我们看到了app的state通过React的props
向下传递到单个的组件,用户的actions声明为回调函数,因此app的逻辑和UI分离开来了.
Redux的工作流介绍
在这一点上,我们的UI是没有交互操作的:尽管我们已经测试了如果一个item如果被设定为completed
,它将给文本划线,但是这里还没有方法邀请用户来完成它:
- state tree通过
props
定义了UI和action回调函数. - 用户的actions,例如点击,被发送到action creator,action被它范式化.
- redux action被传递到reducer实现实际的app逻辑
- reducer更新state tree,dispatch state到store.
- UI根据store里的新state tree来更新UI
设定初始化state
我们的第一个action将会允许我们在Redux store里正确的设置初始化state
,我们将会创建store.
Redux中的action是一个信息的载体(payload).action由一个JSON对象有一个type
属性,描述action到底是做什么的,还有一部分是app需要的信息.在我们的实例中,type被设定为SET_STATE
,我们可以添加一个state对象包含需要的state:
1 | { |
这个action会被dispatch到一个reducer,reducer角色的是识别和实施和action对应的逻辑代码.
让我们为reducer来写单元测试代码test/reducer_spec.js
1 | import {List, Map, fromJS} from 'immutable'; |
为了方便一点,state
使用单纯JS对象,而不是使用Immutable数据结构.让我们的reducer来处理转变.最后,reducer将会优雅的处理undefined
初始化state:test/reducer_spec.js
1 | // ... |
我们的reducer将会匹配接收的actions的type
,如果type是SET_STATE
,当前的state和action运载的state融合在一起:src/reducer.js
1 | import {Map} from 'immutable'; |
现在我们不得不把reducer连接到我们的app,所以当app启动初始化state.这里实际是第一次使用Redux库,安装一下npm install —save redux@3.3.1 react-redux@4.4.1
src/index.jsx
1 | import React from 'react'; |
如果你看看上面的代码段,你可以注意到我们的TodoApp
组件实际是被TodoAppContainer
代替.在Redux里,有两种类型的组件:展示组件和容器.我推荐你阅读一下由Dan Abramov(Redux的作者)写作的高信息量的文章,强调了展示组件和容器的差异性.
如果我想总结得快一点,我将引用Redux 文档的内容:
“展示组件是关于事件的样子(模板和样式),容器组件是关于事情是怎么工作的(数据获取,state更新)”.
所以我们创建store,传递给TodoAppContainer
.然而为了子组件可以使用store,我们把state映射成为React组件TodoApp
的props
.src/components/TodoApp.jsx
1 | // ... |
如果你在浏览器中重新加载app,你应该可以看到它初始化和之前一样,不过现在使用Redux tools.
Redux dev 工具
现在我们已经配置了redux store和reducer.我们可以配置Redux dev tools来展现数据流开发.
首先,获取Redux dev tools Chrome extension
dev tools可以在Store创建的时候可以加载.
src/index.jsx
1 | // ... |
重新加载app,点击Redux图标,有了.
有三个不同的监视器可以使用:Diff监视器,日志监视器,Slider监视器.
使用Action Creators配置我们的actions
切换item的不同状态.
下一步是允许用户在active
和completed
之前切换状态:test/reducer_spec.js
1 | import {List, Map, fromJS} from 'immutable'; |
为了通过这些测试,我们更新reducer:src/reducer.js
1 | // ... |
和SET_STATE
的action同一个地方,我们需要让TodoAppContainer
组件感知到action,所以toggleComplete
回调函数会被传递到TodoItem
组件(实际调用函数的地方).
在Redux中,有标准的方法来做这件事:Action Creators.
action creators是简单的函数,返回合适的action,这些韩式是React的props
的一些映射之一.
让我们创建第一个action creator:src/action_creators.js
1 | export function toggleComplete(itemId) { |
现在,尽管TodoAppcontainer
组件中的connect
函数的调用可以用来获取store,我们告诉组件使用映射props
的回调函数:src/components/TodoApp.jsx
1 | // ... |
重启你的webserver,刷新一下你的浏览器:当当.在条目上点击现在可以切换它的状态.如果你查看Redux dev tools,你可以看到触发的action和后继的更新.
改变目前的过滤器
现在每件事情都已经配置完毕,写其他的action是件小事.我们继续创建你希望的CHANGE_FILTER
action,改变当前state的filter,由此仅仅显示过滤过的条目.
开始创建action creator:src/action_creators.js
1 | // ... |
现在写reducer的单元测试:test/reducer_spec.js
1 | // ... |
关联的reducer函数:src/reducer.js
1 | // ... |
最后我们把changeFilter
回调函数传递给TodoTools
组件:TodoApp.jsx
1 | // ... |
完成了,第一个filter selector工作完美
Item编辑
代码在这里
当用户编辑一个条目,实际上是两个actions触发的三个可能性:
- 用户输入编辑模式:
EDIT_ITEM
- 用户退出编辑模式(不保存变化):
CANCEL_EDITING
- 用户验证他的编辑(保存变化):
DONE_EDITING
我们可以为三个actions编写action creators:src/action_creators.js
1 | // ... |
现在为这些actions编写单元测试:test/reducer_spec.js
1 | // ... |
现在我们可以开发reducer函数,实际操作三个actions:src/reducer.js
1 | function findItemIndex(state, itemId) { |
清除完成,添加和删除条目
三个剩下的action是:
CLEAR_COMPLETED
,在TodoTools
组件中触发,从列表中清除完成的条目ADD_ITEM
,在TodoHeader
中触发,根据用户的的输入文本来添加条目DELETE_ITEM
,相似TodoItem
中调用,删除一个条目
我们现在使用的工作流是:添加action creators,单元测试reducer和代码逻辑,最终通过props传递回调函数:src/action_creators.js
1 | // ... |
test/reducer_spec.js
1 | // ... |
src/reducer.js
1 | function clearCompleted(state) { |
src/components/TodoApp.jsx
1 | // ... |
我们的TodoMVC app现在完成了.
包装起来
这我们的测试驱动的React,Redux&Immutable 技术栈
如果你想了解更多内容,有更多的事情等着你去挖掘
例如: